home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / bin / unattended-upgrade < prev    next >
Encoding:
Text File  |  2009-03-02  |  13.8 KB  |  375 lines

  1. #!/usr/bin/python
  2. #
  3. # (c) 2005-2008 Canonical
  4. # Author: Michael Vogt <michael.vogt@ubuntu.com>
  5. #
  6. # Released under the GPL
  7.  
  8. import apt_inst
  9. import apt_pkg
  10.  
  11. import sys
  12. import os
  13. import string
  14. import datetime
  15. import ConfigParser
  16.  
  17. from optparse import OptionParser
  18. from subprocess import Popen, PIPE
  19.  
  20. import warnings
  21. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  22. import apt
  23. import logging
  24. import subprocess
  25.  
  26. import gettext
  27. from gettext import gettext as _
  28.  
  29. class MyCache(apt.Cache):
  30.     def __init__(self):
  31.         apt.Cache.__init__(self)
  32.     def clear(self):
  33.         self._depcache.Init()
  34.         assert (self._depcache.InstCount == 0 and 
  35.                 self._depcache.BrokenCount == 0 and
  36.         self._depcache.DelCount == 0)
  37.         
  38.  
  39. def is_allowed_origin(pkg, allowed_origins):
  40.     if not pkg.candidateOrigin:
  41.         return False
  42.     for origin in pkg.candidateOrigin:
  43.         for allowed in allowed_origins:
  44.             if origin.origin == allowed[0] and origin.archive == allowed[1]:
  45.                 return True
  46.     return False
  47.  
  48. def check_changes_for_sanity(cache, allowed_origins, blacklist):
  49.     if cache._depcache.BrokenCount != 0:
  50.         return False
  51.     for pkg in cache:
  52.         if pkg.markedDelete:
  53.             return False
  54.         if pkg.markedInstall or pkg.markedUpgrade:
  55.             if not is_allowed_origin(pkg, allowed_origins):
  56.                 return False
  57.             if pkg.name in blacklist:
  58.                 return False
  59.     return True
  60.  
  61. def pkgname_from_deb(debfile):
  62.     # FIXME: add error checking here
  63.     try:
  64.         control = apt_inst.debExtractControl(open(debfile))
  65.         sections = apt_pkg.ParseSection(control)
  66.         return sections["Package"]
  67.     except SystemError, e:
  68.         logging.error("failed to read deb file '%s' (%s)" % (debfile, e))
  69.         # dumb fallback
  70.         return debfile.split("_")[0]
  71.  
  72. def conffile_prompt(destFile):
  73.     logging.debug("check_conffile_prompt('%s')" % destFile)
  74.     pkgname = pkgname_from_deb(destFile)
  75.     status_file = apt_pkg.Config.Find("Dir::State::status")
  76.     parse = apt_pkg.ParseTagFile(open(status_file,"r"))
  77.     while parse.Step() == 1:
  78.         if parse.Section.get("Package") == pkgname:
  79.             logging.debug("found pkg: %s" % pkgname)
  80.             if parse.Section.has_key("Conffiles"):
  81.                 conffiles = parse.Section.get("Conffiles")
  82.                 # Conffiles:
  83.                 # /etc/bash_completion.d/m-a c7780fab6b14d75ca54e11e992a6c11c
  84.                 for line in string.split(conffiles,"\n"):
  85.                     logging.debug("conffile line: %s", line)
  86.                     l = string.split(string.strip(line))
  87.                     file = l[0]
  88.                     md5 = l[1]
  89.                     if len(l) > 2:
  90.                         obs = l[2]
  91.                     else:
  92.                         obs = None
  93.                     if os.path.exists(file) and obs != "obsolete":
  94.                         current_md5 = apt_pkg.md5sum(open(file).read())
  95.                         if current_md5 != md5:
  96.                             return True
  97.     return False
  98.  
  99.  
  100. def dpkg_conffile_prompt():
  101.     if not apt_pkg.Config.has_key("DPkg::Options"):
  102.         return True
  103.     options = apt_pkg.Config.ValueList("DPkg::Options")
  104.     for option in map(string.strip, options):
  105.         if (option == "--force-confold" or
  106.         option == "--force-confnew"):
  107.             return False
  108.     return True
  109.  
  110. def rewind_cache(cache, pkgs_to_upgrade):
  111.     " set the cache back to the state with packages_to_upgrade "
  112.     cache.clear()
  113.     for pkg2 in pkgs_to_upgrade:
  114.         pkg2.markUpgrade()
  115.  
  116. def host():
  117.     return os.uname()[1]
  118.  
  119. # *sigh* textwrap is nice, but it breaks "linux-image" into two
  120. # seperate lines
  121. def wrap(t, width=70, subsequent_indent=""):
  122.     out = ""
  123.     for s in t.split():
  124.         if (len(out)-out.rfind("\n")) + len(s) > width:
  125.             out += "\n" + subsequent_indent
  126.         out += s + " "
  127.     return out
  128.  
  129. def setup_apt_listchanges():
  130.     " deal with apt-listchanges "
  131.     conf = "/etc/apt/listchanges.conf"
  132.     if os.path.exists(conf):
  133.         # check if mail is used by apt-listchanges
  134.         cf = ConfigParser.ConfigParser()
  135.         cf.read(conf)
  136.         if cf.has_section("apt") and cf.has_option("apt","frontend"):
  137.             frontend = cf.get("apt","frontend")
  138.             if frontend == "mail" and os.path.exists("/usr/sbin/sendmail"):
  139.                 # mail frontend and sendmail, we are fine
  140.                 logging.debug("apt-listchanges is set to mail frontend, ignoring")
  141.                 return
  142.     # setup env (to play it safe) and return
  143.     os.putenv("APT_LISTCHANGES_FRONTEND","none");
  144.     
  145.  
  146. def main():
  147.     # init the options
  148.     parser = OptionParser()
  149.     parser.add_option("-d", "--debug",
  150.                       action="store_true", dest="debug", default=False,
  151.                       help=_("print debug messages"))
  152.     (options, args) = parser.parse_args()
  153.     if options.debug:
  154.         logging.getLogger().setLevel(logging.DEBUG)
  155.         pass
  156.  
  157.     #dldir = "/tmp/pyapt-test"
  158.     #try:
  159.     #    os.mkdir(dldir)
  160.     #    os.mkdir(dldir+"/partial")
  161.     #except OSError:
  162.     #    pass
  163.     #apt_pkg.Config.Set("Dir::Cache::archives",dldir)
  164.  
  165.     # format (origin, archive), e.g. ("Ubuntu","dapper-security")
  166.     allowed_origins = map(string.split, apt_pkg.Config.ValueList("Unattended-Upgrade::Allowed-Origins"))
  167.  
  168.     # pkgs that are (for some reason) not save to install
  169.     blacklisted_pkgs = apt_pkg.Config.ValueList("Unattended-Upgrade::Package-Blacklist")
  170.     logging.info(_("Initial blacklisted packages: %s"), " ".join(blacklisted_pkgs))
  171.     logging.info(_("Starting unattended upgrades script"))
  172.  
  173.     # display available origin
  174.     logging.info(_("Allowed origins are: %s") % map(str,allowed_origins))
  175.     
  176.     # get a cache
  177.     cache = MyCache()
  178.     if cache._depcache.BrokenCount > 0:
  179.         print _("Cache has broken packages, exiting")
  180.         logging.error(_("Cache has broken packages, exiting"))
  181.         sys.exit(1)
  182.     # speed things up with latest apt
  183.     actiongroup = apt_pkg.GetPkgActionGroup(cache._depcache)
  184.  
  185.     # find out about the packages that are upgradable (in a allowed_origin)
  186.     pkgs_to_upgrade = []
  187.     pkgs_kept_back = []
  188.     for pkg in cache:
  189.         if options.debug and pkg.isUpgradable:
  190.             logging.debug("Checking: %s (%s)" % (pkg.name,map(str, pkg.candidateOrigin)))
  191.         if (pkg.isUpgradable and 
  192.         is_allowed_origin(pkg,allowed_origins)):
  193.             try:
  194.                 pkg.markUpgrade()
  195.                 if check_changes_for_sanity(cache, allowed_origins,
  196.                                             blacklisted_pkgs):
  197.                     pkgs_to_upgrade.append(pkg)
  198.                 else:
  199.                     logging.debug("sanity check failed")
  200.                     rewind_cache(cache, pkgs_to_upgrade)
  201.                     pkgs_kept_back.append(pkg)
  202.             except SystemError, e:
  203.                 # can't upgrade
  204.                 logging.warning(_("package '%s' upgradable but fails to be marked for upgrade (%s)") % e)
  205.                 rewind_cache(cache, pkgs_to_ugprade)
  206.                 pkgs_kept_back.append(pkg)
  207.                 
  208.  
  209.     pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])
  210.     logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)
  211.            
  212.     # download what looks good
  213.     if options.debug:
  214.         fetcher = apt_pkg.GetAcquire(apt.progress.TextFetchProgress())
  215.     else:
  216.         fetcher = apt_pkg.GetAcquire()
  217.     list = apt_pkg.GetPkgSourceList()
  218.     list.ReadMainList()
  219.     recs = cache._records
  220.     pm = apt_pkg.GetPackageManager(cache._depcache)
  221.     try:
  222.         pm.GetArchives(fetcher,list,recs)
  223.     except SystemError, e:
  224.         logging.error(_("GetArchives() failed: '%s'") % e)
  225.     res = fetcher.Run()
  226.  
  227.     if dpkg_conffile_prompt():
  228.         # now check the downloaded debs for conffile conflicts and build
  229.         # a blacklist
  230.         for item in fetcher.Items:
  231.             logging.debug("%s" % item)
  232.             if item.Status == item.StatError:
  233.                 print _("An error ocured: '%s'") % item.ErrorText
  234.                 logging.error(_("An error ocured: '%s'") % item.ErrorText)
  235.             if item.Complete == False:
  236.                 print _("The URI '%s' failed to download, aborting") % item.DescURI
  237.                 logging.error(_("The URI '%s' failed to download, aborting") % item.DescURI)
  238.                 sys.exit(1)
  239.             if item.IsTrusted == False:
  240.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  241.             if conffile_prompt(item.DestFile):
  242.                 # FIXME: skip package (means to re-run the whole marking again
  243.                 # and making sure that the package will not be pulled in by
  244.                 # some other package again!
  245.                 logging.warning(_("Package '%s' has conffile prompt and needs to be upgraded manually") % pkgname_from_deb(item.DestFile))
  246.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  247.  
  248.  
  249.         # redo the selection about the packages to upgrade based on the new
  250.         # blacklist
  251.         logging.debug("blacklist: %s" % blacklisted_pkgs)
  252.         # find out about the packages that are upgradable (in a allowed_origin)
  253.         if len(blacklisted_pkgs) > 0:
  254.             cache.clear()
  255.             old_pkgs_to_upgrade = pkgs_to_upgrade[:]
  256.             pkgs_to_upgrade = []
  257.             for pkg in old_pkgs_to_upgrade:
  258.                 logging.debug("Checking (blacklist): %s" % (pkg.name))
  259.                 pkg.markUpgrade()
  260.                 if check_changes_for_sanity(cache, allowed_origins,
  261.                                             blacklisted_pkgs):
  262.                      pkgs_to_upgrade.append(pkg)
  263.                 else:
  264.                     logging.info(_("package '%s' not upgraded") % pkg.name)
  265.                     cache.clear()
  266.                     for pkg2 in pkgs_to_upgrade:
  267.                         pkg2.markUpgrade()
  268.     else:
  269.         logging.debug("dpkg is configured not to cause conffile prompts")
  270.  
  271.     logging.debug("InstCount=%i DelCount=%i BrokenCout=%i" % (cache._depcache.InstCount, cache._depcache.DelCount, cache._depcache.BrokenCount))
  272.  
  273.     # check what we have
  274.     if len(pkgs_to_upgrade) == 0:
  275.         logging.info(_("No packages found that can be upgraded unattended"))
  276.         sys.exit(0)    
  277.  
  278.     # do the install based on the new list of pkgs
  279.     pkgs = " ".join([pkg.name for pkg in pkgs_to_upgrade])
  280.     logging.info(_("Packages that are upgraded: %s" % pkgs))
  281.  
  282.     # set debconf to NON_INTERACTIVE, redirect output
  283.     os.putenv("DEBIAN_FRONTEND","noninteractive");
  284.     setup_apt_listchanges()
  285.     
  286.     # redirect to log
  287.     REDIRECT_INPUT = os.devnull
  288.     fd = os.open(REDIRECT_INPUT, os.O_RDWR)
  289.     os.dup2(fd,0)
  290.  
  291.     now = datetime.datetime.now()
  292.     logfile_dpkg = logdir+'unattended-upgrades-dpkg_%s.log' % now.isoformat('_')
  293.     logging.info(_("Writing dpkg log to '%s'") % logfile_dpkg)
  294.     fd = os.open(logfile_dpkg, os.O_RDWR|os.O_CREAT, 0644)
  295.     os.dup2(fd,1)
  296.     os.dup2(fd,2)
  297.     
  298.     # enable debugging
  299.     if options.debug:
  300.         apt_pkg.Config.Set("Debug::pkgDPkgPM","1")
  301.     # create a new package-manager. the blacklist may have changed
  302.     # the markings in the depcache
  303.     pm = apt_pkg.GetPackageManager(cache._depcache)
  304.     if not pm.GetArchives(fetcher,list,recs):
  305.         logging.error(_("pm.GetArchives() failed"))
  306.     # run the fetcher again (otherwise local file:// 
  307.     # URIs are unhappy (see LP: #56832)
  308.     res = fetcher.Run()
  309.     # now do the actual install
  310.     try:
  311.         res = pm.DoInstall()
  312.     except SystemError,e:
  313.         logging.error(_("Installing the upgrades failed!"))
  314.         logging.error(_("error message: '%s'") % e)
  315.         res = False
  316.                 
  317.     if res == pm.ResultFailed:
  318.         logging.error(_("dpkg returned a error! See '%s' for details") % logfile_dpkg)
  319.     else:
  320.         logging.info(_("All upgrades installed"))
  321.  
  322.     # check if we need to send a mail
  323.     email = apt_pkg.Config.Find("Unattended-Upgrade::Mail", "")
  324.     if email != "":
  325.         if not os.path.exists("/usr/bin/mail"):
  326.             logging.error(_("No '/usr/bin/mail', can not send mail. "
  327.                             "You probably want to install the 'mailx' package."))
  328.             return
  329.         logging.debug("Sending mail with '%s' to '%s'" % (logfile_dpkg, email))
  330.         mail = subprocess.Popen(["/usr/bin/mail",
  331.                                  "-s", _("unattended-upgrades result "
  332.                                          "for '%s'") % host(), 
  333.                                  email],
  334.                                 stdin=subprocess.PIPE)
  335.         s = _("Unattended upgrade returned: %s\n\n") % (res != pm.ResultFailed)
  336.         s += _("Packages that are upgraded:\n")
  337.         s += " " + wrap(pkgs, 70, " ")
  338.         s += "\n"
  339.         if pkgs_kept_back:
  340.             s += _("Packages with upgradable origin but kept back:\n")
  341.             s += " " + wrap(" ".join([pkg.name for pkg in pkgs_kept_back]), 70, " ")
  342.             s += "\n"
  343.         s += "\n"
  344.         s += _("Package installation log:")
  345.         s += open(logfile_dpkg).read()
  346.         mail.stdin.write(s)
  347.         mail.stdin.close()
  348.         ret = mail.wait()
  349.         logging.debug("mail returned: %s" % ret)
  350.  
  351.  
  352. if __name__ == "__main__":
  353.     localesApp="unattended-upgrades"
  354.     localesDir="/usr/share/locale"
  355.     gettext.bindtextdomain(localesApp, localesDir)
  356.     gettext.textdomain(localesApp)
  357.  
  358.     if os.getuid() != 0:
  359.         print _("You need to be root to run this application")
  360.         sys.exit(1)
  361.     
  362.     if not os.path.exists("/var/log/unattended-upgrades"):
  363.         os.makedirs("/var/log/unattended-upgrades")
  364.  
  365.     # init the logging
  366.     logdir = apt_pkg.Config.FindDir("APT::UnattendedUpgrades::LogDir",
  367.                                     "/var/log/unattended-upgrades/")
  368.     logfile = logdir+apt_pkg.Config.Find("APT::UnattendedUpgrades::LogFile",
  369.                                          "unattended-upgrades.log")
  370.     logging.basicConfig(level=logging.INFO,
  371.                         format='%(asctime)s %(levelname)s %(message)s',
  372.                         filename=logfile)
  373.     # run the main code
  374.     main()
  375.